// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Linq;

namespace SnapObjects.Data
{
    /// <summary>
    ///     A sealed class which represents a dynamic model object that you do not need to specify properties
    ///     and property datatypes.It can be used to map the result set dynamically after a SQL query is 
    ///     executed in the database.
    /// </summary>
    public sealed class DynamicModel
    {
        private readonly IDynamicModelBase _model;
        private readonly IModelAccessor<IDynamicModelBase> _baseAccessor;

        internal DynamicModel(IDynamicModelBase model,
            IModelAccessor<IDynamicModelBase> baseAccessor,
            DynamicModelFactory dynamicModelFactory)
        {
            _model = model;
            _baseAccessor = baseAccessor;
            this.DynamicModelFactory = dynamicModelFactory;
        }

        internal bool IsCreatedFrom(DynamicModelFactory dynamicModelFactory)
        {
            return this.DynamicModelFactory == dynamicModelFactory;
        }

        internal bool IsCreatedFromSameFactory(DynamicModel dynamicModel)
        {
            return this.DynamicModelFactory == dynamicModel.DynamicModelFactory;
        }

        internal DynamicModelFactory DynamicModelFactory { get; }

        /// <summary>
        ///     Gets the number of the properties used in the current DynamicModel object.
        /// </summary>
        public int PropertyCount =>
            _baseAccessor.ModelMeta.PropertyCount;

        /// <summary>
        ///     Gets an IReadOnlyList<IPropertyInfo> object which contains information about all of 
        ///     the properties used in the current DynamicModel object.
        /// </summary>
        public IReadOnlyList<IPropertyInfo> Properties =>
                                _baseAccessor.ModelMeta.Properties
                                                .Select(o => (IPropertyInfo)o)
                                                .OrderBy(o => o.Index)
                                                .ToList();

        /// <summary>
        ///     Gets or sets the value of the property at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the property to get or set value.</param>
        /// <returns>The value of the specified property.</returns>
        public object this[int index]
        {
            get => _model[index];
            set => _model[index] = value;
        }

        /// <summary>
        ///     Gets or sets the value of the specified property (by name).
        /// </summary>
        /// <param name="columnName">The name of the property to get or set value.</param>
        /// <returns>The value of the specified property.</returns>
        public object this[string columnName]
        {
            get => this.GetValue(columnName);
            set => this.SetValue(columnName, value);
        }

        /// <summary>
        ///     Gets the zero-based index of the property specified by the property name.
        /// </summary>
        /// <param name="name">The name of the property for which you want to get the zero-based index.</param>
        /// <returns></returns>
        public int IndexOf(string name)
        {
            return _baseAccessor.IndexOf(name);
        }

        /// <summary>
        ///     Gets the value of the specified property (by index) in the DynamicModel object.
        /// </summary>
        /// <typeparam name="TValue">The data type of the value you want to get from the specified property.</typeparam>
        /// <param name="index">The zero-based index of the property to get value.</param>
        /// <returns>Returns the value of the specified property.</returns>
        public TValue GetValue<TValue>(int index)
        {
            return _model.GetValue<TValue>(index);
        }

        /// <summary>
        ///     Gets the value of the specified property (by index) in the DynamicModel object.
        /// </summary>
        /// <param name="index">The zero-based index of the property to get value.</param>
        /// <returns>Returns the value of the specified property.</returns>
        public object GetValue(int index)
        {
            return _model.GetValue(index);
        }

        /// <summary>
        ///     Gets the value of the specified property (by name) in the DynamicModel object, 
        ///     and casts it to the specified data type.
        /// </summary>
        /// <typeparam name="TValue">The data type of the value you want to get from the specified property.</typeparam>
        /// <param name="columnName">The name of the property to get value.</param>
        /// <returns>Returns the value of the specified property.</returns>
        public TValue GetValue<TValue>(string columnName)
        {
            var index = _baseAccessor.IndexOf(columnName);

            return _model.GetValue<TValue>(index);
        }

        /// <summary>
        ///     Gets the value of the specified property (by name) in the DynamicModel object, 
        ///     and casts it to the specified data type.
        /// </summary>
        /// <param name="columnName">The name of the property to get value.</param>
        /// <returns>Returns the value of the specified property.</returns>
        public object GetValue(string columnName)
        {
            return _baseAccessor.GetValue(_model, columnName);
        }

        /// <summary>
        ///     Sets the value for the specified property (by index) in the DynamicModel object.
        /// </summary>
        /// <param name="index">The zero-based index of the property in the DynamicModel object to set value.</param>
        /// <param name="value">The value to set to the specified property.</param>
        public void SetValue(int index, object value)
        {
            _model.SetValue(index, value);
        }

        /// <summary>
        ///     Sets the value for the specified property (by name) in the DynamicModel object.
        /// </summary>
        /// <param name="columnName">The name of the property in the DynamicModel object to set value.</param>
        /// <param name="value">The value to set to the specified property.</param>
        public void SetValue(string columnName, object value)
        {
            _baseAccessor.SetValue(_model, columnName, value);
        }

        /// <summary>
        ///     Sets one or more values to the properties in the DynamicModel object according to 
        ///     the order of the property index.
        /// </summary>
        /// <param name="values">One or more values to set to the properties in the DynamicModel object.</param>
        public void SetValues(object[] values)
        {
            _model.SetValues(values);
        }

        /// <summary>
        ///     Copies data from another DynamicModel object to the current DynamicModel object.
        /// </summary>
        /// <param name="dynamicModel">The DynamicModel object from which to copy data.</param>
        public void CopyFrom(DynamicModel dynamicModel)
        {
            if (!this.IsCreatedFromSameFactory(dynamicModel))
            {
                throw new InvalidOperationException();
            }

            for (var i = 0; i < this.PropertyCount; i++)
            {
                this.SetValue(i, dynamicModel.GetValue(i));
            }
        }

        /// <summary>
        ///     Copies the values of the properties within the specified scope of another DynamicModel 
        ///     object to the property of the corresponding location of the current DynamicModel object.
        /// </summary>
        /// <param name="dynamicModel">The DynamicModel object from which to copy data.</param>
        /// <param name="propertyStart">The zero-based index of the property where the copy starts.</param>
        /// <param name="propertyEnd">The zero-based index of the property where the copy ends.</param>
        public void CopyFrom(DynamicModel dynamicModel, int propertyStart, int propertyEnd)
        {
            if (!this.IsCreatedFromSameFactory(dynamicModel))
            {
                throw new InvalidOperationException();
            }

            if (propertyStart < 0 || propertyStart > this.PropertyCount - 1 ||
                propertyEnd < 0 || propertyEnd > this.PropertyCount - 1 || propertyStart > propertyEnd)
            {
                throw new IndexOutOfRangeException(
                    $"propertyStart={propertyStart},propertyEnd={propertyEnd}");
            }

            for (var i = propertyStart; i <= propertyEnd; i++)
            {
                this.SetValue(i, dynamicModel.GetValue(i));
            }
        }

        /// <summary>
        ///     Creates a new DynamicModel object which is a copy of the current DynamiclModel instance.
        /// </summary>
        /// <returns>Returns a copy of the current DynamiclModel instance.</returns>
        public DynamicModel Clone()
        {
            return new DynamicModel(
                _baseAccessor.Clone(_model),
                _baseAccessor,
                this.DynamicModelFactory);
        }

    }
}
